/*
* Copyright 2013, Rimero Solutions
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rimerosolutions.ant.git;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.RootDoc;
import com.sun.tools.doclets.standard.Standard;
/**
* Quick and very dirty doclet for Ant tasks documentation.
* Self-Contained for now, no template engine or external dependencies.
*
* @author Yves Zoundi
*/
public final class AntTaskDoclet extends Standard {
private static final String ENCODING_UTF_8 = "UTF-8";
private static final String ANT_TASK_CLASS_NAME = "org.apache.tools.ant.Task";
private static String destDir;
private static String doctitle = "Ant tasks documentation";
private static String header = "Ant tasks documentation";
private static String windowtitle = "Ant tasks documentation";
private static String bottom;
private static String overview;
private static final Logger LOG = Logger.getLogger(AntTaskDoclet.class.getName());
private static final List<String> ELEMENT_PREFIXES = Arrays.asList("create", "add", "addConfigured");
private static final List<String> ATTRIBUTE_PREFIXES = Arrays.asList("set");
private static final String TAG_ANTDOC_NOTREQUIRED = "antdoc.notrequired";
private static Map<String, ClassData> classDataMap = new HashMap<String, ClassData>();
private static final String HTML_INDEX_PAGE = "index.html";
private static final String HTML_HEADER_PAGE = "header.html";
private static final String HTML_BODY_PAGE = "body.html";
private static final String HTML_NAV_PAGE = "nav.html";
private static final class ElementData {
String name;
String description;
static ElementData fromMethodDoc(MethodDoc methodDoc) {
ElementData data = new ElementData();
data.name = sanitizeName(methodDoc.name(), ELEMENT_PREFIXES);
data.description = methodDoc.commentText();
return data;
}
}
private static final class AttributeData {
String name;
String description;
boolean required;
static AttributeData fromMethodDoc(MethodDoc methodDoc) {
AttributeData data = new AttributeData();
data.description = methodDoc.commentText();
data.required = !(methodDoc.tags(TAG_ANTDOC_NOTREQUIRED).length > 0);
data.name = sanitizeName(methodDoc.name(), ATTRIBUTE_PREFIXES);
return data;
}
}
private static final class ClassData {
String qualifiedName;
String simpleName;
String description;
List<String> parentClassDatas = new ArrayList<String>();
List<AttributeData> attributesList = new ArrayList<AttributeData>();
List<ElementData> elementsList = new ArrayList<ElementData>();
boolean hidden;
}
static interface WriterCallback {
void doWithWriter(Writer w) throws IOException;
}
private static void copyFile(File sourceFile, File destFile) throws IOException {
try (InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(destFile)) {
byte[] buf = new byte[4096];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
}
public static final boolean start(RootDoc root) {
readDestDirOption(root.options());
try {
writeContents(root.classes());
} catch (IOException ioe) {
LOG.log(Level.SEVERE, "Ant tasks documentation failed", ioe);
return false;
}
return true;
}
public static void readDestDirOption(String options[][]) {
for (int i = 0; i < options.length; i++) {
String[] opt = options[i];
if (opt[0].equals("-d")) {
destDir = opt[1];
} else if (opt[0].equals("-header")) {
header = opt[1];
} else if (opt[0].equals("-doctitle")) {
doctitle = opt[1];
} else if (opt[0].equals("-windowtitle")) {
windowtitle = opt[1];
} else if (opt[0].equals("-overview")) {
overview = opt[1];
} else if (opt[0].equals("-bottom")) {
bottom = opt[1];
}
}
}
private static String sanitizeName(String objectName, List<String> prefixes) {
String newName = objectName;
for (String prefix : prefixes) {
if (newName.startsWith(prefix)) {
String methodName = newName.substring(prefix.length() + 1);
return (("" + newName.charAt(prefix.length())).toLowerCase() + methodName).toLowerCase();
}
}
return newName;
}
static void withWriter(File f, WriterCallback wc) throws IOException {
try (Writer w = new OutputStreamWriter(new FileOutputStream(f), ENCODING_UTF_8)) {
wc.doWithWriter(w);
}
}
private static void writeIndexPage() throws IOException {
withWriter(new File(new File(destDir), HTML_INDEX_PAGE), new WriterCallback() {
@Override
public void doWithWriter(Writer w) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("<html><head>");
sb.append(htmlElement("title", windowtitle));
sb.append("</head>");
sb.append("<frameset rows=\"15%,*\">");
sb.append(" <frame src=\"");
sb.append(HTML_HEADER_PAGE);
sb.append("\">");
sb.append(" <frameset cols=\"25%,75%\">");
sb.append("<frame src=\"");
sb.append(HTML_NAV_PAGE);
sb.append("\">");
sb.append("<frame name=\"bodycontents\" src=\"");
sb.append(HTML_BODY_PAGE);
sb.append("\">");
sb.append("</frameset></frameset></html>");
sb.append("</html>");
w.write(sb.toString());
}
});
}
private static void writeHeaderPage() throws IOException {
withWriter(new File(new File(destDir), HTML_HEADER_PAGE), new WriterCallback() {
@Override
public void doWithWriter(Writer w) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("<html><head>");
sb.append(htmlElement("title", windowtitle));
sb.append("</head><body>");
sb.append(htmlElement("h1", header));
sb.append("</body></html>");
w.write(sb.toString());
}
});
}
private static void writeBodyPage() throws IOException {
if (overview == null) {
withWriter(new File(new File(destDir), HTML_BODY_PAGE), new WriterCallback() {
@Override
public void doWithWriter(Writer w) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("<html><head>");
sb.append(htmlElement("title", windowtitle));
sb.append("</head><body>");
sb.append(htmlElement("div", doctitle));
sb.append("</body></html>");
w.write(sb.toString());
}
});
} else {
copyFile(new File(overview), new File(new File(destDir), HTML_BODY_PAGE));
}
}
private static void writeNavPage() throws IOException {
withWriter(new File(new File(destDir), HTML_NAV_PAGE), new WriterCallback() {
@Override
public void doWithWriter(Writer w) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("<html><head><title>Nav</title></head><body><div><ul>");
for (Map.Entry<String, ClassData> entry : classDataMap.entrySet()) {
if (!entry.getValue().hidden) {
sb.append("<li>");
sb.append("<a target=\"bodycontents\" href=\"");
sb.append(entry.getKey()).append(".html");
sb.append("\">");
sb.append(entry.getValue().simpleName);
sb.append("</a></li>");
}
}
sb.append("</ul></div></body></html>");
w.write(sb.toString());
}
});
}
private static String htmlElement(String tagName, Object contents) {
return new StringBuilder().
append('<').
append(tagName).
append('>').
append(contents).
append("</").
append(tagName).
append('>').
toString();
}
private static void writeHtmlTasks() throws IOException {
Map<String, ClassData> classDataCopy = new HashMap<String, AntTaskDoclet.ClassData>(classDataMap);
for (Map.Entry<String, ClassData> entry : classDataMap.entrySet()) {
String classDocName = entry.getKey();
ClassData classData = entry.getValue();
if (classData.hidden) {
continue;
}
File outputFile = new File(new File(destDir), classDocName + ".html");
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(outputFile), ENCODING_UTF_8);
StringBuilder header = new StringBuilder();
header.append("<html><head><title>classDocName</title><body><div>");
w.write(header.toString());
header = new StringBuilder();
header.append(htmlElement("h1", classData.simpleName));
header.append("<hr/>");
header.append(htmlElement("h2", "Description"));
header.append(htmlElement("div", classData.description));
w.write(header.toString());
List<AttributeData> attributesCopy = new ArrayList<AntTaskDoclet.AttributeData>(classData.attributesList);
List<ElementData> elementsCopy = new ArrayList<AntTaskDoclet.ElementData>(classData.elementsList);
if (!classData.parentClassDatas.isEmpty()) {
for (String parent : classData.parentClassDatas) {
attributesCopy.addAll(classDataCopy.get(parent).attributesList);
elementsCopy.addAll(classDataCopy.get(parent).elementsList);
}
}
if (!attributesCopy.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("<h2>Attributes</h2>");
sb.append("<table border='1'>");
sb.append("<tr>");
sb.append("<th>Name</th>").append("<th>Description</th>").append("<th>Required</th>");
sb.append("</tr>");
for (AttributeData attr : attributesCopy) {
sb.append("<tr>");
sb.append(htmlElement("td", attr.name));
sb.append(htmlElement("td", attr.description));
sb.append(htmlElement("td", attr.required));
sb.append("</tr>");
}
sb.append("</table>");
w.write(sb.toString());
}
if (!elementsCopy.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("<h2>Nested elements</h2>");
sb.append("<table border='1'>");
sb.append("<tr>");
sb.append("<th>Name</th>").append("<th>Description</th>");
sb.append("</tr>");
for (ElementData attr : elementsCopy) {
sb.append("<tr>");
sb.append(htmlElement("td", attr.name));
sb.append(htmlElement("td", attr.description));
sb.append("</tr>");
}
sb.append("</table>");
w.write(sb.toString());
}
header = new StringBuilder();
header.append("<div><p>").append(bottom).append("</p></div>");
w.write(header.toString());
header = new StringBuilder();
header.append("</div></body></html>");
w.write(header.toString());
w.flush();
w.close();
}
}
private static void writeHtmlToOutputDir() throws IOException {
writeBodyPage();
writeNavPage();
writeHeaderPage();
writeIndexPage();
writeHtmlTasks();
}
private static List<String> collectParentClassesNames(ClassDoc doc) {
List<String> parents = new ArrayList<String>();
ClassDoc currentParent = doc.superclass();
while (currentParent != null) {
if (currentParent.qualifiedTypeName().equals(ANT_TASK_CLASS_NAME)) {
break;
}
parents.add(currentParent.qualifiedTypeName());
currentParent = currentParent.superclass();
}
return parents;
}
private static boolean isAntTask(ClassDoc classDoc) {
ClassDoc currentParent = classDoc.superclass();
while (currentParent != null) {
if (currentParent.qualifiedTypeName().equals(ANT_TASK_CLASS_NAME)) {
return true;
}
currentParent = currentParent.superclass();
}
return false;
}
private static void registerClassData(ClassDoc doc) {
ClassData data = new ClassData();
Scanner sc = new Scanner(doc.commentText());
data.description = sc.nextLine();
sc.close();
data.qualifiedName = doc.qualifiedTypeName();
data.simpleName = doc.name();
data.hidden = doc.isAbstract();
data.parentClassDatas.addAll(collectParentClassesNames(doc));
for (MethodDoc methodDoc : doc.methods()) {
if (!isHiddenMethodDoc(methodDoc)) {
if (isTaskAttribute(methodDoc)) {
data.attributesList.add(AttributeData.fromMethodDoc(methodDoc));
} else if (isTaskElement(methodDoc)) {
data.elementsList.add(ElementData.fromMethodDoc(methodDoc));
}
}
}
classDataMap.put(data.qualifiedName, data);
}
private static boolean isHiddenMethodDoc(MethodDoc methodDoc) {
return methodDoc.isPrivate() || methodDoc.isProtected() || methodDoc.isAbstract();
}
private static boolean namePrefixMatches(String name, List<String> prefixes) {
for (String prefix : prefixes) {
if (name.startsWith(prefix)) {
return true;
}
}
return false;
}
private static boolean isTaskAttribute(MethodDoc methodDoc) {
return namePrefixMatches(methodDoc.name(), ATTRIBUTE_PREFIXES);
}
private static boolean isTaskElement(MethodDoc methodDoc) {
return namePrefixMatches(methodDoc.name(), ELEMENT_PREFIXES);
}
private static void writeContents(ClassDoc[] classDocs) throws IOException {
for (ClassDoc classDoc : classDocs) {
if (isAntTask(classDoc)) {
registerClassData(classDoc);
}
}
writeHtmlToOutputDir();
}
public static LanguageVersion languageVersion() {
return LanguageVersion.JAVA_1_5;
}
}